/**
 * This file is part of Port@l
 * Port@l 3.0 - Portal Engine and Management System
 * Copyright (C) 2010  Isotrol, SA.  http://www.isotrol.com
 *
 * Port@l is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Port@l is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Port@l.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.isotrol.impe3.gui.common.util;


import java.util.Date;

import com.extjs.gxt.ui.client.core.El;
import com.extjs.gxt.ui.client.event.ComponentEvent;
import com.extjs.gxt.ui.client.event.DatePickerEvent;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.IconButtonEvent;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.event.SelectionListener;
import com.extjs.gxt.ui.client.widget.Component;
import com.extjs.gxt.ui.client.widget.DatePicker;
import com.extjs.gxt.ui.client.widget.HorizontalPanel;
import com.extjs.gxt.ui.client.widget.Html;
import com.extjs.gxt.ui.client.widget.LayoutContainer;
import com.extjs.gxt.ui.client.widget.Window;
import com.extjs.gxt.ui.client.widget.button.IconButton;
import com.extjs.gxt.ui.client.widget.form.Field;
import com.extjs.gxt.ui.client.widget.form.TextField;
import com.extjs.gxt.ui.client.widget.form.TextField.TextFieldMessages;
import com.extjs.gxt.ui.client.widget.layout.FormLayout;
import com.extjs.gxt.ui.client.widget.tips.ToolTipConfig;
import com.google.inject.Inject;
import com.isotrol.impe3.gui.common.i18n.GuiCommonMessages;
import com.isotrol.impe3.gui.common.i18n.GuiCommonStyles;


/**
 * @author Andrei Cojocaru
 * 
 */
public final class FormSupport {

	/**
	 * Template for a rendered icon.<br/>
	 */
	private static final String ICON_TEMPLATE = "<img src='img/${FILE_NAME}' class='${CSS_CLASS}'/>";
	/**
	 * "File name" pattern to replace in {@link #ICON_TEMPLATE template.}<br/>
	 */
	private static final String PATTERN_FILE_NAME = "\\$\\{FILE_NAME\\}";
	/**
	 * "Css class" pattern to replace in {@link #ICON_TEMPLATE template.}<br/>
	 */
	private static final String PATTERN_CSS_CLASS = "\\$\\{CSS_CLASS\\}";

	/**
	 * Minimum layout width that should be used in <b>panels set with the Layout generated by</b>
	 * {@link #getStandardLayout()}.<br/> User can nevertheless use greater values. Smaller values are not recommended.
	 */
	public static final int RECOMMENDED_WIDTH = 520;

	/**
	 * Minimum width for panels & windows that contain a FormLayout with the default configuration
	 */
	public static final int MINIMUM_WIDTH = 360;

	/**
	 * Field width.<br/>
	 */
	public static final int FIELD_WIDTH = 250;

	/**
	 * Width in px for the date picker popup.<br/>
	 */
	private static final int DATE_PICKER_WIDTH = 180;

	/*
	 * Injected deps
	 */
	/**
	 * Buttons helper.<br/>
	 */
	private Buttons buttons = null;

	/**
	 * Validates non empty strings<br/>
	 */
	private NonEmptyStringValidator nonEmptyStringsValidator = null;

	/**
	 * Validates when configured events get fired.<br/>
	 */
	private ValidatorListener validatorListener = null;

	/**
	 * Generic styles bundle.<br/>
	 */
	private GuiCommonStyles styles = null;

	/**
	 * Messages bundle.<br/>
	 */
	private GuiCommonMessages messages = null;

	/**
	 * Default constructor.
	 */
	public FormSupport() {
	}

	/**
	 * Adds a horizontal panel to the passed parent with: <ol> <li>a panel with FormLayout to the left, for fields;</li>
	 * <li>a panel to the right, for icons.</li> </ol> The horizontal panel has a 10 px margin bottom.
	 * @param parent parent container for the added elements
	 * @return left container at index 0 and right container at index 1.<br/>
	 */
	public LayoutContainer[] addFieldContainerSkeleton(LayoutContainer parent) {
		return addFieldContainerSkeleton(parent, true, Constants.FIELD_WIDTH);
	}

	/**
	 * Adds a horizontal panel to the passed parent with: <ol> <li>a panel with FormLayout to the left, for fields;</li>
	 * <li>a panel to the right, for icons.</li> </ol> The horizontal panel has a 10 px margin bottom.
	 * @param parent parent container for the added elements
	 * @param fieldWidth the width in pixels for the field.
	 * @return left container at index 0 and right container at index 1.<br/>
	 */
	public LayoutContainer[] addFieldContainerSkeleton(LayoutContainer parent, int fieldWidth) {
		return addFieldContainerSkeleton(parent, true, fieldWidth);
	}

	/**
	 * Same behaviour as {@link #addFieldContainerSkeleton(LayoutContainer, boolean, int)}, taking the default
	 * recommended width for fields.
	 * @param parent
	 * @param withMarginBottom
	 * @return
	 */
	public LayoutContainer[] addFieldContainerSkeleton(LayoutContainer parent, boolean withMarginBottom) {
		return addFieldContainerSkeleton(parent, withMarginBottom, RECOMMENDED_WIDTH);
	}

	/**
	 * Adds an horizontal panel to the passed parent with: <ol> <li>a panel with FormLayout to the left, for
	 * fields;</li> <li>a panel to the right, for icons.</li> </ol>
	 * @param parent
	 * @param withMarginBottom if <code>true</code>, adds a 10px margin bottom to the horizontal panel.
	 * @return array with left container at index 0 and right container at index 1.<br/>
	 */
	public LayoutContainer[] addFieldContainerSkeleton(LayoutContainer parent, boolean withMarginBottom, int fieldWidth) {
		HorizontalPanel lc = new HorizontalPanel();
		lc.setAutoHeight(true);
		lc.setBorders(false);

		if (withMarginBottom) {
			lc.addStyleName(styles.marginBottom10px());
		}

		FormLayout formLayout = getStandardLayout(fieldWidth);

		LayoutContainer left = new LayoutContainer(formLayout);
		left.setAutoHeight(true);
		// make place for the invalid icon:
		left.setWidth(formLayout.getLabelWidth() + formLayout.getDefaultWidth() + 23);
		lc.add(left);

		LayoutContainer right = new HorizontalPanel();
		right.setAutoHeight(true);
		right.addStyleName(styles.paddingLeft4px());
		lc.add(right);

		parent.add(lc);

		return new LayoutContainer[] {left, right};
	}

	/**
	 * Returns a standard FormLayout. Does not change the default 10px padding.<br/> The field width is set as the
	 * default {@link Constants#FIELD_WIDTH}
	 * 
	 * @return
	 */
	public FormLayout getStandardLayout() {
		return getStandardLayout(true, Constants.FIELD_WIDTH);
	}

	/**
	 * Returns a standard FormLayout.<br/>
	 * @param addMarginBottom
	 * @return the created layout.
	 */
	public FormLayout getStandardLayout(boolean addMarginBottom) {
		return getStandardLayout(addMarginBottom, Constants.FIELD_WIDTH);
	}

	/**
	 * Same as {@link #getStandardLayout()}, but permits specifying a width for the fields.<br/>
	 * @param fieldWidth width in pixels for the form fields.
	 * @return
	 */
	public FormLayout getStandardLayout(int fieldWidth) {
		return getStandardLayout(true, fieldWidth);
	}

	/**
	 * Returns a standard FormLayout. Does not change the default 10px padding.<br/>
	 * @param addMarginBottom if <code>true</code>, adds a 10px margin on the bottom of the layout.
	 * @param fieldWidth width in pixels for the form fields.
	 * @return created layout.
	 */
	public FormLayout getStandardLayout(boolean addMarginBottom, int fieldWidth) {
		FormLayout fl = new FormLayout();
		fl.setLabelWidth(Constants.LABEL_WIDTH);
		fl.setDefaultWidth(fieldWidth);
		if (addMarginBottom) {
			fl.setExtraStyle(styles.marginBottom10px());
		}

		return fl;
	}

	/**
	 * Inserts a date field and a date picker<br/>
	 * @return
	 */
	public TextField<Date> addDateField(LayoutContainer container) {
		final TextField<Date> field = new TextField<Date>();

		assert container.getLayout() instanceof FormLayout : "The Layout must be a FormLayout!!!";

		FormLayout layout = (FormLayout) container.getLayout();
		LayoutContainer[] lr = addFieldContainerSkeleton(container,
			(int) (layout.getDefaultWidth() * Constants.FIFTY_PERCENT_FLOAT));
		lr[0].add(field);

		SelectionListener<IconButtonEvent> lIcon = new SelectionListener<IconButtonEvent>() {
			@Override
			public void componentSelected(IconButtonEvent ce) {
				showDatePicker(field);
			}
		};

		IconButton bShowDatePicker = buttons
			.createGenericIconButton(messages.ttChooseDate(), styles.iCalendar(), lIcon);
		lr[1].add(bShowDatePicker);

		return field;
	}

	/**
	 * <br/>
	 * @param field
	 */
	private void showDatePicker(final TextField<Date> field) {
		final Window w = new Window();
		w.setModal(true);
		w.setAutoHeight(true);
		w.setWidth(DATE_PICKER_WIDTH);
		w.setResizable(false);

		DatePicker datePicker = new DatePicker();
		w.add(datePicker);

		Date theDate = field.getValue();
		if (theDate == null) {
			theDate = new Date();
		}
		datePicker.setValue(theDate);

		datePicker.addListener(Events.Select, new Listener<DatePickerEvent>() {
			public void handleEvent(DatePickerEvent be) {
				w.hide();
				field.setValue(be.getDatePicker().getValue());
			}
		});

		// FIXME a nicer solution should be found. IE compatibility problems.
		datePicker.addListener(Events.Render, new Listener<ComponentEvent>() {
			public void handleEvent(ComponentEvent be) {
				Component c = be.getComponent();
				El el = c.el();
				el.child("table").setWidth(Constants.HUNDRED_PERCENT);
			}
		});

		w.show();
	}

	/**
	 * Adds an icon to the passed panel. The icon must be under 'img' directory.<br/>
	 * 
	 * @param title icon title
	 * @param iconContainer container for the icon
	 * @param fileName icon graphic resource name.
	 */
	public void addIcon(String title, LayoutContainer iconContainer, String fileName) {

		if (title != null && !title.equals("")) {
			Html icon = new Html(ICON_TEMPLATE.replaceAll(PATTERN_FILE_NAME, fileName).replaceAll(PATTERN_CSS_CLASS,
				styles.marginRight4px()));
			icon.setToolTip(new ToolTipConfig(title));

			iconContainer.add(icon);
		}
	}

	/**
	 * Sets the <code>readonly</code> flag to <code>true</code> and adds visual styles to the passed field.<br/>
	 * 
	 * @param field
	 */
	public void configReadOnly(Field<?> field) {
		field.setReadOnly(true);
		field.addStyleName(styles.readonlyField());
	}

	/**
	 * Sets the <code>readonly</code> flag and adds or removes visual styles to the passed field.<br/>
	 * 
	 * @param field
	 * @param readonly
	 */
	public void configReadOnly(Field<?> field, boolean readonly) {
		field.setReadOnly(readonly);
		if(readonly) {
			field.addInputStyleName(styles.readonlyField());
		} else {
			field.removeInputStyleName(styles.readonlyField());
		}
		
	}

	/**
	 * Configures the text field as required: blank/empty strings not allowed, validates on render, and "Required field"
	 * messages are shown when blank/empty.<br/>
	 * @param field
	 */
	@SuppressWarnings("unchecked")
	public void configRequired(TextField<?> field) {
		field.setAllowBlank(false);
		field.setValidator(nonEmptyStringsValidator);
		field.addListener(Events.Render, validatorListener);

		TextFieldMessages fieldMessages = field.getMessages();
		fieldMessages.setInvalidText(messages.vmRequired());
		fieldMessages.setBlankText(messages.vmRequired());
	}

	/*
	 * Injectors
	 */
	/**
	 * Injects the generic styles bundle.
	 * @param styles
	 */
	@Inject
	public void setStyles(GuiCommonStyles styles) {
		this.styles = styles;
	}

	/**
	 * Injects the validator for non empty strings.<br/>
	 * @param nonEmptyStringsValidator
	 */
	@Inject
	public void setNonEmptyStringsValidator(NonEmptyStringValidator nonEmptyStringsValidator) {
		this.nonEmptyStringsValidator = nonEmptyStringsValidator;
	}

	/**
	 * Injects the listener that validates on render.<br/>
	 * @param validatorListener
	 */
	@Inject
	public void setValidatorListener(ValidatorListener validatorListener) {
		this.validatorListener = validatorListener;
	}

	/**
	 * Injects the messages bundle.
	 * @param messages the messages to set
	 */
	@Inject
	public void setMessages(GuiCommonMessages messages) {
		this.messages = messages;
	}

	/**
	 * Injects the buttons support.<br/>
	 * @param buttons
	 */
	@Inject
	public final void setButtons(Buttons buttons) {
		this.buttons = buttons;
	}
}
